<?php
/**
 * Magento
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@magentocommerce.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Magento to newer
 * versions in the future. If you wish to customize Magento for your
 * needs please refer to http://www.magentocommerce.com for more information.
 *
 * @category    Mage
 * @package     Mage_Connect
 * @copyright   Copyright (c) 2009 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
 * @license     http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */

/**
 * Class to work with HTTP protocol using sockets
 *
 * @category    Mage
 * @package     Mage_Connect
 * @author      Magento Core Team <core@magentocommerce.com>
 */
class Mage_HTTP_Client_Socket
    implements Mage_HTTP_IClient
{
	/**
	 * Hostname
	 * @var string
	 */
	private $_host = 'localhost';

	/**
	 * Port
	 * @var int
	 */
	private $_port = 80;

	/**
	 * Stream resource 
	 * @var object
	 */
	private $_sock = null;
	
	/**
	 * Request headers
	 * @var array
	 */
	private $_headers = array();
	
	
	/**
	 * Fields for POST method - hash
	 * @var array
	 */
	private $_postFields = array();
	
	/**
	 * Request cookies 
	 * @var array
	 */
	private $_cookies = array();
	
	/**
	 * Response headers
	 * @var array
	 */
	private $_responseHeaders = array();
	
	/**
	 * Response body
	 * @var string
	 */
	private $_responseBody = '';
	
	/**
	 * Response status
	 * @var int
	 */
	private $_responseStatus = 0;
	
	
	/**
	 * Request timeout
	 * @var int
	 */
	private $_timeout = 300;
	
	/**
	 * TODO
	 * @var int
	 */
	private $_redirectCount = 0;


	/**
	 * Set request timeout, msec
	 * 
	 * @param int $value 
	 */
	public function setTimeout($value)
	{
		$this->_timeout = (int) $value;
	}

	/**
	 * Constructor 
	 * @param string $host 
	 * @param int $port
	 */
	public function __construct($host = null, $port = 80)
	{
		if($host) {
			$this->connect($host, (int) $port);
		}
	}

	/**
	 * Set connection params
	 *
     * @param string $host 
     * @param int $port
	 */
	public function connect($host, $port = 80)
	{
		$this->_host = $host;
		$this->_port = (int) $port;

	}
	
	/**
	 * Disconnect
	 */
	public function disconnect()
	{
		@fclose($this->_sock);
	}

	/**
	 * Set headers from hash
	 
	 * @param array $headers
	 */	
	public function setHeaders($headers)
	{
		$this->_headers = $headers;

	}
	
	/**
	 * Add header
	 * 
	 * @param $name name, ex. "Location"
	 * @param $value value ex. "http://google.com"
	 */
	public function addHeader($name, $value)
	{
		$this->_headers[$name] = $value;

	}
	
	/**
	 * Remove specified header
	 * 
	 * @param string $name
	 */
	public function removeHeader($name)
	{
		unset($this->_headers[$name]);

	}

	/**
	 * Authorization: Basic header
	 * Login credentials support
	 * 
	 * @param string $login username
	 * @param string $pass password
	 */
	public function setCredentials($login, $pass)
	{
		$val= base64_encode( "$login:$pass" );
		$this->addHeader( "Authorization", "Basic $val" );
	}

	/**
	 * Add cookie
	 * 
	 * @param string $name
	 * @param string $value 	 
	 */
	public function addCookie($name, $value)
	{
		$this->_cookies[$name] = $value;
	}

	/**
	 * Remove cookie 
	 * 
	 * @param string $name
	 */	
	public function removeCookie($name)
	{
		unset($this->_cookies[$name]);
	}

	/**
	 * Set cookies array
	 * 	 
	 * @param array $cookies
	 */
	public function setCookies($cookies)
	{
		$this->_cookies = $cookies;
	}

	/**
	 * Clear cookies
	 */
	public function removeCookies()
	{
		$this->setCookies(array());
	}

	
	/**
	 * Make GET request
	 * 
	 * @param string $uri full uri path
	 */
	public function get($uri)
	{	
        $this->makeRequest("GET",$this->parseUrl($uri));		
	}

	/**
	 * Set host, port from full url
	 * and return relative url 
	 * 
	 * @param string $uri ex. http://google.com/index.php?a=b
	 * @return string ex. /index.php?a=b
	 */
	protected function parseUrl($uri) 
	{
		$parts = parse_url($uri);
		if(!empty($parts['user']) && !empty($parts['pass'])) {
			$this->setCredentials($parts['user'], $parts['pass']);
		}
		if(!empty($parts['port'])) {
			$this->_port = (int) $parts['port'];
		}
		
		if(!empty($parts['host'])) {
			$this->_host = $parts['host'];
		} else {
			throw new InvalidArgumentException("Uri doesn't contain host part"); 
		}
		
		
		if(!empty($parts['path'])) {
			$requestUri = $parts['path'];
		} else {
			throw new InvalidArgumentException("Uri doesn't contain path part");
		}
		if(!empty($parts['query'])) {
			$requestUri .= "?".$parts['query'];
		}
		return $requestUri;
	}
	
	/**
	 * Make POST request
	 */
	public function post($uri, $params)
	{
		$this->makeRequest("POST", $this->parseUrl($uri), $params);
	}


	/**
	 * Get response headers
	 * 
	 * @return array	 
	 */
	public function getHeaders()
	{
		return $this->_responseHeaders;
	}
	

	/**
	 * Get response body
	 * 
	 * @return string	
	 */
	public function getBody()
	{
		return $this->_responseBody;
	}

	/**
	 * Get cookies response hash 
	 *
	 * @return array	 
	 */
	public function getCookies()
	{
		if(empty($this->_responseHeaders['Set-Cookie'])) {
			return array();
		}
		$out = array();
		foreach( $this->_responseHeaders['Set-Cookie'] as $row) {
			$values = explode("; ", $row);
			$c = count($values);
			if(!$c) {
				continue;
			}
			list($key, $val) = explode("=", $values[0]);
			if(is_null($val)) {
				continue;
			}
			$out[trim($key)] = trim($val);
		}
		return $out;
	}


	/**
	 * Get cookies array with details
	 * (domain, expire time etc)
	 * @return array
	 */
	public function getCookiesFull()
	{
		if(empty($this->_responseHeaders['Set-Cookie'])) {
			return array();
		}
		$out = array();
		foreach( $this->_responseHeaders['Set-Cookie'] as $row) {
			$values = explode("; ", $row);
			$c = count($values);
			if(!$c) {
				continue;
			}
			list($key, $val) = explode("=", $values[0]);
			if(is_null($val)) {
				continue;
			}
			$out[trim($key)] = array('value'=>trim($val));
			array_shift($values);
			$c--;
			if(!$c) {
				continue;
			}
			for($i = 0; $i<$c; $i++) {
				list($subkey, $val) = explode("=", $values[$i]);
				$out[trim($key)][trim($subkey)] = trim($val);
			}
		}
		return $out;
	}

	/**
	 * Process response headers
	 */
	protected function processResponseHeaders()
	{
		$crlf = "\r\n";
		$this->_responseHeaders = array();
		while (!feof($this->_sock)) {
			$line = fgets($this->_sock, 1024);
			if($line === $crlf) {
				return;
			}
			$name = $value = '';
			$out = explode(": ", trim($line), 2);
			if(count($out) == 2) {
				$name = $out[0];
				$value = $out[1];
			}		
    		if(!empty($value)) {
				if($name == "Set-Cookie") {
				    if(!isset($this->_responseHeaders[$name])) {
                        $this->_responseHeaders[$name] = array();
                    }
					$this->_responseHeaders[$name][] = $value;
				} else {
					$this->_responseHeaders[$name] = $value;
				}
			}
		}
	}

	/**
	 * Process response body
	 */
	protected function processResponseBody()
	{
		$this->_responseBody = '';

		while (!feof($this->_sock)) {
			$this->_responseBody .= @fread($this->_sock, 1024);
		}
	}

	/**
	 * Process response	  
	 */
	protected function processResponse()
	{
		$response = '';
		$responseLine = trim(fgets($this->_sock, 1024));

		$line = explode(" ", $responseLine, 3);
		if(count($line) != 3) {
			return $this->doError("Invalid response line returned from server: ".$responseLine);
		}
		$this->_responseStatus = intval($line[1]);
		$this->processResponseHeaders();

		$this->processRedirect();

		$this->processResponseBody();
	}


	/**
	 * Process redirect
	 */
	protected function processRedirect()
	{
		// TODO: implement redircets support
	}

	
	/**
	 * Get response status code
	 * @see lib/Mage/HTTP/Mage_HTTP_Client#getStatus()
	 */
	public function getStatus()
	{
		return $this->_responseStatus;
	}

	/**
	 * Make request
	 * @param string $method 
	 * @param string $uri
	 * @param array $params
	 * @return null
	 */
	protected function makeRequest($method, $uri, $params = array())
	{
		$errno = $errstr = '';
		$this->_sock = @fsockopen($this->_host, $this->_port, $errno, $errstr, $this->_timeout);
		if(!$this->_sock) {
			return $this->doError(sprintf("[errno: %d] %s", $errno, $errstr));
		}

		$crlf = "\r\n";
		$isPost = $method == "POST";

		$appendHeaders = array();
		$paramsStr = false;
		if($isPost && count($params)) {
			$paramsStr = http_build_query($params);
			$appendHeaders['Content-type'] = 'application/x-www-form-urlencoded';
			$appendHeaders['Content-length'] = strlen($paramsStr);
		}

		$out = "{$method} {$uri} HTTP/1.1{$crlf}";
		$out .= $this->headersToString($appendHeaders);
		$out .= $crlf;
		if($paramsStr) {
			$out .= $paramsStr.$crlf;
		}
		
		fwrite($this->_sock, $out);
		$this->processResponse();
	}
	
	/**
	 * Throw error excpetion
	 * @param $string
	 * @throws Exception
	 */
	public function doError($string)
	{
		throw new Exception($string);
	}

	/**
	 * Convert headers hash to string 
	 * @param $delimiter
	 * @param $append
	 * @return string
	 */
	protected function headersToString($append = array())
	{
		$headers = array();
		$headers["Host"] = $this->_host;
		$headers['Connection'] = "close";
		$headers = array_merge($headers, $this->_headers, $append);
		$str = array();
		foreach ($headers as $k=>$v) {
			$str []= "$k: $v\r\n";
		}
		return implode($str);
	}

	/**
	 * TODO
	 */
	public function setOptions($arr)
	{
		// Stub
	}
	
	/**
	 * TODO
	 */
	public function setOption($name, $value)
	{
		// Stub
	}

}